#pragma once

#include <cstdio>
#include <ostream>
#include <thread>
#include <atomic>
#include <SDL2/SDL.h>

extern "C" {
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}

#include "ThreadSafeQueue.h"

class Renderer {
public:
    Renderer(ThreadSafeQueue<AVFrame*>& frame_queue, std::atomic<bool>& decode_done, std::atomic<bool>& render_done)
        : frame_queue(frame_queue), quit(false), paused(false), decode_done(decode_done), render_done(render_done) {}

    bool init(int width, int height, double frame_rate) {
        frame_delay = static_cast<int>(1000 / frame_rate);

        if (SDL_Init(SDL_INIT_VIDEO) < 0) {
            return false;
        }

        window = SDL_CreateWindow("Video Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, 0);
        if (!window) {
            return false;
        }

        renderer = SDL_CreateRenderer(window, -1, 0);
        if (!renderer) {
            return false;
        }

        texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, width, height);
        if (!texture) {
            return false;
        }

        sws_ctx = sws_getContext(width, height, AV_PIX_FMT_YUV420P, width, height, AV_PIX_FMT_RGBA, SWS_BILINEAR, nullptr, nullptr, nullptr);
        return sws_ctx != nullptr;
    }

    void start() {
        render_thread = std::thread(&Renderer::render, this);
    }

    void stop() {
        quit = true;
        std::printf("Try to stop render thread\n");
        std::fflush(stdout);
        if (render_thread.joinable()) {
            render_thread.join();
        }
        std::printf("Render thread stopped\n");
        std::fflush(stdout);
    }

    void toggle_pause() {
        if (paused) {
            std::printf("Resume\n");
        } else {
            std::printf("Pause\n");
        }
        paused = !paused;
    }

    ~Renderer() {
        if (sws_ctx) {
            sws_freeContext(sws_ctx);
        }

        if (texture) {
            SDL_DestroyTexture(texture);
        }

        if (renderer) {
            SDL_DestroyRenderer(renderer);
        }

        if (window) {
            SDL_DestroyWindow(window);
        }

        SDL_Quit();

        std::printf("Renderer destroyed\n");
        std::fflush(stdout);
    }

private:
    void render() {
        Uint32 frame_start;
        int delay;

        while (!quit) {
            frame_start = SDL_GetTicks();

            if (paused) {
                SDL_Delay(10);
                continue;
            }

            if (!frame_queue.empty()) {
                AVFrame* frame = frame_queue.pop();
                AVFrame* frame_rgba = av_frame_alloc();
                int num_bytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, frame->width, frame->height, 1);
                uint8_t* buffer = (uint8_t*)av_malloc(num_bytes * sizeof(uint8_t));
                av_image_fill_arrays(frame_rgba->data, frame_rgba->linesize, buffer, AV_PIX_FMT_RGBA, frame->width, frame->height, 1);
                sws_scale(sws_ctx, (uint8_t const* const*)frame->data, frame->linesize, 0, frame->height, frame_rgba->data, frame_rgba->linesize);

                SDL_UpdateTexture(texture, nullptr, frame_rgba->data[0], frame_rgba->linesize[0]);
                SDL_RenderClear(renderer);
                SDL_RenderCopy(renderer, texture, nullptr, nullptr);
                SDL_RenderPresent(renderer);

                av_free(buffer);
                av_frame_free(&frame_rgba);
                av_frame_free(&frame);
                std::printf("Remain Frame: %ld\n", frame_queue.size());
                std::fflush(stdout);
            } else if (decode_done) {
                quit = true;
                break;
            } else {
                SDL_Delay(10);
                std::printf("Render Sleep\n");
                std::fflush(stdout);
            }

            delay = frame_delay - (SDL_GetTicks() - frame_start);
            if (delay > 0) {
                SDL_Delay(delay);
            }
        }
    
        render_done = true;
        std::printf("Render Done\n");
        std::fflush(stdout);
    }

    SDL_Window* window = nullptr;
    SDL_Renderer* renderer = nullptr;
    SDL_Texture* texture = nullptr;
    struct SwsContext* sws_ctx = nullptr;
    ThreadSafeQueue<AVFrame*>& frame_queue;
    std::atomic<bool> quit;
    std::atomic<bool> paused;
    std::atomic<bool>& decode_done;
    std::atomic<bool>& render_done;
    std::thread render_thread;
    int frame_delay;
};
